package dirsync

import (
	"context"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/edsrzf/mmap-go"
)

// FileProgressCallback 定义进度回调函数类型
//
// 参数:
//   - copiedSize: 已复制的文件大小
//   - totalSize: 总文件大小
//   - progressPercent: 当前进度百分比(0-100)
type FileProgressCallback func(FileProgress)

// 复制文件并通过回调函数显示进度
func copyFileWithProgressCallback(ctx context.Context, src, dst string, overwrite bool, progressCallback FileProgressCallback) error {
	// 检查目标文件是否存在
	dstFileInfo, err := os.Stat(dst)
	if err == nil && !overwrite {
		return fmt.Errorf("目标文件已存在，且未启用覆盖模式")
	}

	// 打开源文件
	srcFile, err := os.OpenFile(src, os.O_RDONLY, 0644)
	if err != nil {
		return fmt.Errorf("无法打开源文件： %v", err)
	}
	defer srcFile.Close()

	// 获取源文件信息
	fileInfo, err := srcFile.Stat()
	if err != nil {
		return fmt.Errorf("无法获取源文件信息： %v", err)
	}
	fileSize := fileInfo.Size()
	fileMode := fileInfo.Mode()
	fileTime := fileInfo.ModTime()

	// 如果文件大小为 0，直接创建一个空文件
	if fileSize == 0 {
		dstFile, err := os.Create(dst)
		if err != nil {
			return fmt.Errorf("无法创建目标文件 %v", err)
		}
		defer dstFile.Close()

		// 保留文件权限和时间戳
		if err := os.Chmod(dst, fileMode); err != nil {
			return fmt.Errorf("无法设置文件权限: %v", err)
		}
		if err := os.Chtimes(dst, time.Now(), fileTime); err != nil {
			return fmt.Errorf("无法设置文件时间戳: %v", err)
		}
		return nil
	}

	// 对于小文件和中等大小的文件使用内存映射
	const maxMmapSize = int64(1 * 1024 * 1024 * 1024) // 1GB
	if fileSize <= maxMmapSize {
		err = copyWithMmap(ctx, srcFile, dst, fileSize, fileMode, fileTime, dstFileInfo, progressCallback)
		if err == nil {
			return nil
		}
		// 如果内存映射失败，回退到标准IO
		fmt.Printf("内存映射复制失败，回退到标准IO: %v\n", err)
	}

	// 对于大文件或内存映射失败的情况，使用标准IO
	return copyWithStandardIO(ctx, srcFile, dst, fileSize, fileMode, fileTime, progressCallback)
}

// 使用内存映射复制文件
func copyWithMmap(ctx context.Context, srcFile *os.File, dst string, fileSize int64, fileMode os.FileMode, fileTime time.Time, dstFileInfo os.FileInfo, progressCallback FileProgressCallback) error {
	// 尝试使用内存映射
	srcData, err := mmap.Map(srcFile, mmap.RDONLY, 0)
	if err != nil {
		return fmt.Errorf("无法 mmap 源文件 %v", err)
	}
	defer srcData.Unmap()

	// 检查目标文件是否与源文件大小相同，如果是则检查内容是否一致
	if dstFileInfo != nil && dstFileInfo.Size() == fileSize {
		dstFile, err := os.OpenFile(dst, os.O_RDWR, 0644)
		if err != nil {
			return fmt.Errorf("无法打开目标文件： %v", err)
		}
		defer dstFile.Close()

		dstData, err := mmap.Map(dstFile, mmap.RDWR, 0)
		if err != nil {
			return fmt.Errorf("无法 mmap 目标文件 %v", err)
		}
		defer dstData.Unmap()

		if checkFileConsistency(srcData, dstData, fileSize) {
			// 文件内容一致，无需复制
			// 更新文件权限和时间戳
			if err := os.Chmod(dst, fileMode); err != nil {
				return fmt.Errorf("无法设置文件权限: %v", err)
			}
			if err := os.Chtimes(dst, time.Now(), fileTime); err != nil {
				return fmt.Errorf("无法设置文件时间戳: %v", err)
			}
			return nil
		}
	}

	// 创建目标文件
	dstFile, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("无法创建目标文件 %v", err)
	}
	defer dstFile.Close()

	// 设置文件大小
	if err := dstFile.Truncate(fileSize); err != nil {
		return fmt.Errorf("无法截断目标文件 %v", err)
	}

	// 映射目标文件
	dstData, err := mmap.Map(dstFile, mmap.RDWR, 0)
	if err != nil {
		return fmt.Errorf("无法 mmap 目标文件 %v", err)
	}
	defer dstData.Unmap()

	// 分块复制文件内容
	blockSize := int64(1024 * 1024) // 1MB
	var copied int64
	
	// 添加速度计算
	startTime := time.Now()
	lastUpdateTime := startTime
	var lastCopied int64

	for copied < fileSize {
		select {
		case <-ctx.Done():
			// 如果上下文被取消，终止复制
			return ctx.Err()
		default:
			remaining := fileSize - copied
			if remaining < blockSize {
				blockSize = remaining
			}

			copy(dstData[copied:copied+blockSize], srcData[copied:copied+blockSize])
			copied += blockSize

			// 只有在提供回调函数时才更新进度
			if progressCallback != nil {
				// 计算速度（每秒更新一次）
				now := time.Now()
				elapsed := now.Sub(lastUpdateTime)
				
				var speedBytesPerSec int64
				if elapsed >= time.Second {
					// 计算每秒字节数
					speedBytesPerSec = int64(float64(copied-lastCopied) / elapsed.Seconds())
					lastUpdateTime = now
					lastCopied = copied
				} else if elapsed > 0 {
					// 如果时间间隔小于1秒，使用总体平均速度
					totalElapsed := now.Sub(startTime).Seconds()
					if totalElapsed > 0 {
						speedBytesPerSec = int64(float64(copied) / totalElapsed)
					}
				}
				
				progressCallback(FileProgress{
					CopiedSize:      copied,
					TotalSize:       fileSize,
					ProgressPercent: float64(copied) / float64(fileSize) * 100,
					SpeedBytesPerSec: speedBytesPerSec,
				})
			}
		}
	}

	// 保留文件权限和时间戳
	if err := os.Chmod(dst, fileMode); err != nil {
		return fmt.Errorf("无法设置文件权限: %v", err)
	}
	if err := os.Chtimes(dst, time.Now(), fileTime); err != nil {
		return fmt.Errorf("无法设置文件时间戳: %v", err)
	}

	return nil
}

// 使用标准IO复制文件
func copyWithStandardIO(ctx context.Context, srcFile *os.File, dst string, fileSize int64, fileMode os.FileMode, fileTime time.Time, progressCallback FileProgressCallback) error {
	// 创建目标文件
	dstFile, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("无法创建目标文件 %v", err)
	}
	defer dstFile.Close()

	// 重置文件指针到开始位置
	if _, err := srcFile.Seek(0, 0); err != nil {
		return fmt.Errorf("无法重置文件指针: %v", err)
	}

	// 分块复制文件内容
	buffer := make([]byte, 1024*1024) // 1MB 缓冲区
	var copied int64
	var readErr error
	
	// 添加速度计算
	startTime := time.Now()
	lastUpdateTime := startTime
	var lastCopied int64

	for readErr != io.EOF {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			n, err := srcFile.Read(buffer)
			readErr = err

			if err != nil && err != io.EOF {
				return fmt.Errorf("读取源文件失败: %v", err)
			}

			if n > 0 {
				if _, err := dstFile.Write(buffer[:n]); err != nil {
					return fmt.Errorf("写入目标文件失败: %v", err)
				}

				copied += int64(n)

				// 更新进度
				if progressCallback != nil {
					// 计算速度（每秒更新一次）
					now := time.Now()
					elapsed := now.Sub(lastUpdateTime)
					
					var speedBytesPerSec int64
					if elapsed >= time.Second {
						// 计算每秒字节数
						speedBytesPerSec = int64(float64(copied-lastCopied) / elapsed.Seconds())
						lastUpdateTime = now
						lastCopied = copied
					} else if elapsed > 0 {
						// 如果时间间隔小于1秒，使用总体平均速度
						totalElapsed := now.Sub(startTime).Seconds()
						if totalElapsed > 0 {
							speedBytesPerSec = int64(float64(copied) / totalElapsed)
						}
					}
					
					progressCallback(FileProgress{
						CopiedSize:      copied,
						TotalSize:       fileSize,
						ProgressPercent: float64(copied) / float64(fileSize) * 100,
						SpeedBytesPerSec: speedBytesPerSec,
					})
				}
			}
		}
	}

	// 保留文件权限和时间戳
	if err := os.Chmod(dst, fileMode); err != nil {
		return fmt.Errorf("无法设置文件权限: %v", err)
	}
	if err := os.Chtimes(dst, time.Now(), fileTime); err != nil {
		return fmt.Errorf("无法设置文件时间戳: %v", err)
	}

	return nil
}

// CopyFileSimple 复制单个文件并通过回调函数显示进度（简化版，不支持取消）
//
// 参数:
//   - src: 源文件路径
//   - dst: 目标文件路径
//   - overwrite: 是否覆盖目标文件
//   - progressCallback: 进度回调函数，可为nil
//
// 返回:
//   - error: 如果复制过程中发生错误，返回相应的错误信息
func CopyFileSimple(src, dst string, overwrite bool, progressCallback FileProgressCallback) error {
	return CopyFile(context.Background(), src, dst, overwrite, progressCallback)
}
// CopyFile 复制单个文件并通过回调函数显示进度
// CopyFile 复制单个文件并通过回调函数显示进度
//
// 参数:
//   - ctx: 上下文，用于取消复制操作
//   - src: 源文件路径
//   - dst: 目标文件路径
//   - overwrite: 是否覆盖目标文件
//   - progressCallback: 进度回调函数，可为nil
//
// 返回:
//   - error: 如果复制过程中发生错误，返回相应的错误信息
func CopyFile(ctx context.Context, src, dst string, overwrite bool, progressCallback FileProgressCallback) error {
	return copyFileWithProgressCallback(ctx, src, dst, overwrite, progressCallback)
}
